package com.qs.commontools.utils.security;


import com.qs.commontools.constants.SecurityConstant;
import com.qs.commontools.utils.base.TransformUtils;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.math.ec.ECCurve;
import org.bouncycastle.math.ec.ECPoint;

import java.io.*;
import java.math.BigInteger;
import java.security.SecureRandom;

/**
 * SM2工具类。
 * <br>非对称加密，基于ECC。该算法已公开。由于该算法基于ECC，故其签名速度与秘钥生成速度都快于RSA。ECC 256位（SM2采用的就是ECC 256位的一种）安全强度比RSA 2048位高，但运算速度快于RSA。
 *
 * @author qiusuo
 * @since 2021-09-22
 */
public class SM2Utils {

    private String privateKey;

    private String publicKey;

    private static ECCurve.Fp curve;
    private static ECPoint G;
    private static ECDomainParameters ecc_bc_spec;
    private static SecureRandom random = new SecureRandom();

    static {
        curve = new ECCurve.Fp(SecurityConstant.SM2_P, SecurityConstant.SM2_A, SecurityConstant.SM2_B);
        G = curve.createPoint(SecurityConstant.SM2_GX, SecurityConstant.SM2_GY);
        ecc_bc_spec = new ECDomainParameters(curve, G, SecurityConstant.SM2_N);
    }

    private SM2Utils() {

    }

    private SM2Utils(String publicKey, String privateKey) {
        this.publicKey = publicKey;
        this.privateKey = privateKey;
    }

//    /**
//     * 获得SM2工具实例。
//     *
//     * @return SM2工具实例
//     */
//    public static SM2Utils getInstance() {
//        return new SM2Utils();
//    }
//
//    /**
//     * 获得SM2工具实例的同时设置私钥和公钥。
//     *
//     * @param publicKey 公钥
//     * @param privateKey 私钥
//     * @return SM2工具实例
//     */
//    public  static SM2Utils getInstance(String publicKey, String privateKey) {
//        return new SM2Utils(publicKey, privateKey);
//    }





//
//    /**
//     * 加密。
//     *
//     * @return String 加密后的结果
//     * @throws UnsupportedEncodingException 不支持的编码形式
//     */
//    public String encrypt(byte[] publicKey, byte[] data) throws IOException {
//
//
//        byte[] source = new byte[data.length];
//        System.arraycopy(data, 0, source, 0, data.length);
//
//        SM2Cipher cipher = new SM2Cipher();
//        SM2 sm2 = SM2.Instance();
//        ECPoint userKey = sm2.ecc_curve.decodePoint(publicKey);
//
//        ECPoint c1 = cipher.Init_enc(sm2, userKey);
//        cipher.Encrypt(source);
//        byte[] c3 = new byte[32];
//        cipher.Dofinal(c3);
//
//        // System.out.println("C1 " + Util.byteToHex(c1.getEncoded()));
//        // System.out.println("C2 " + Util.byteToHex(source));
//        // System.out.println("C3 " + Util.byteToHex(c3));
//        //C1 C2 C3拼装成加密字串
//        return ByteService.byteToHex(c1.getEncoded(false)) + ByteService.byteToHex(source) + ByteService.byteToHex(c3);
//
//    }
//
//
//    /**
//     * 解密。
//     * @return 解密后的结果
//     * @throws DecryptErrorException 解密异常
//     */
//    public String decrypt(byte[] privateKey, String data) throws IOException {
//
//        //加密字节数组转换为十六进制的字符串
////        String data = ByteService.byteToHex(encryptedData);
//        byte[] encryptedData = TransformUtils.hexStrToByte(data);
//
//        /***分解加密字串
//         * （C1 = C1标志位2位 + C1实体部分128位 = 130）
//         * （C3 = C3实体部分64位  = 64）
//         * （C2 = encryptedData.length * 2 - C1长度  - C2长度）
//         */
//        byte[] c1Bytes = ByteService.hexToByte(data.substring(0,130));
//        int c2Len = encryptedData.length - 97;
//        byte[] c2 = ByteService.hexToByte(data.substring(130,130 + 2 * c2Len));
//        byte[] c3 = ByteService.hexToByte(data.substring(130 + 2 * c2Len,194 + 2 * c2Len));
//
//        SM2 sm2 = SM2.Instance();
//        BigInteger userD = new BigInteger(1, privateKey);
//
//        //通过C1实体字节来生成ECPoint
//        ECPoint c1 = sm2.ecc_curve.decodePoint(c1Bytes);
//        SM2Cipher cipher = new SM2Cipher();
//        cipher.Init_dec(userD, c1);
//        cipher.Decrypt(c2);
//        cipher.Dofinal(c3);
//
//        //返回解密结果
//        return TransformUtils.hexByteToStr(c2);
//    }


    /**
     * 生成私钥。
     *
     * @return 私钥
     */
    public static BigInteger createPrivateKey(){
        BigInteger d = random(SecurityConstant.SM2_N.subtract(new BigInteger(SecurityConstant.NUM_STRING_ONE)));
        return d;
    }

    /**
     * 生成公钥，一定要通过私钥生成公钥。
     * @param d 私钥
     * @return 公钥
     */
    public static ECPoint createPublicKey(BigInteger d){
        return G.multiply(d).normalize();
    }

    /**
     * 获得私钥的字符串形式。
     *
     * @param p 私钥（BigInteger类型）
     * @return 字符串形式的私钥
     */
    public static String toStrPrivateKey(BigInteger p) {
        return TransformUtils.hexByteToStr(p.toByteArray());
    }

    /**
     * 获得私钥的BigInteger形式。
     *
     * @param p 私钥（String类型）
     * @return 字符串形式的私钥
     */
    public static BigInteger toBigIntegerPrivateKey(String p) {
        return new BigInteger(TransformUtils.hexStrToByte(p));
    }



    /**
     * 获得公钥的字符串形式。
     *
     * @param p 公钥（ECPoint类型）
     * @return 字符串形式的公钥
     */
    public static String toStrPublicKey(ECPoint p) {
        return TransformUtils.hexByteToStr(p.getEncoded(false));
    }


//    /**
//     * 获得公钥的ECPoint形式。
//     *
//     * @param p 公钥（String类型）
//     * @return 字符串形式的公钥
//     */
//    public static String toECPointPublicKey(String p) {
//        byte[] bytes = TransformUtils.hexStrToByte(p);
//
//        return TransformUtils.hexByteToStr(p.getEncoded(false));
//    }

    /**
     * 导出私钥到本地。
     * @param privateKey 私钥对象
     * @param path 本地保存路径包含文件名和后缀
     */
    public static void exportPrivateKey(BigInteger privateKey, String path) {
        File file = new File(path);
        try {
            if (!file.exists()) {
                file.createNewFile();
            }
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
            oos.writeObject(privateKey);
            oos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 导出公钥到本地。
     * @param publicKey 公钥对象
     * @param path 本地保存路径包含文件名和后缀
     */
    public static void exportPublicKey(ECPoint publicKey, String path) {
        File file = new File(path);
        try {
            if (!file.exists()) {
                file.createNewFile();
            }
            byte buffer[] = publicKey.getEncoded(false);
            FileOutputStream fos = new FileOutputStream(file);
            fos.write(buffer);
            fos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 从本地导入私钥。
     * @param path 本地保存私钥的文件路径
     * @return 私钥
     */
    public static BigInteger importPrivateKey(String path) {
        File file = new File(path);
        try {
            if (!file.exists()) {
                return null;
            }
            FileInputStream fis = new FileInputStream(file);
            ObjectInputStream ois = new ObjectInputStream(fis);
            BigInteger res = (BigInteger) (ois.readObject());
            ois.close();
            fis.close();
            return res;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 从本地导入公钥。
     *
     * @param path 本地保存公钥的文件路径
     * @return 公钥
     */
    public static ECPoint importPublicKey(String path) {
        File file = new File(path);
        try {
            if (!file.exists()) {
                return null;
            }
            FileInputStream fis = new FileInputStream(file);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();

            byte buffer[] = new byte[16];
            int size;
            while ((size = fis.read(buffer)) != -1) {
                baos.write(buffer, 0, size);
            }
            fis.close();
            return curve.decodePoint(baos.toByteArray());
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 随机数生成器
     */
    private static BigInteger random(BigInteger max) {
        BigInteger r = new BigInteger(SecurityConstant.NUM_INTEGER_TWO_HUNDRED_AND_FIFTY_SIX, random);
        while (r.compareTo(max) >= SecurityConstant.NUM_INTEGER_ZORE) {
            r = new BigInteger(SecurityConstant.NUM_INTEGER_ONE_HUNDRED_AND_TWENTY_EIGHT, random);
        }
        return r;
    }

}
